源码分析 mybatis SQL执行流程及参数绑定过程

持久层框架封装了jdbc的底层代码,我们需要提供的即sql、映射规则、pojo
研究mybatis,即

  1. mybatis怎么读取配置文件创建SqlSessionFactory
  2. SqlSession运行过程

这里更多探究的是SqlSession运行过程

第一点 - mybatis 源码分析–sql大致执行流程

[TOC]

一.准备

1. 映射器组成

  • MappedStatement,用于保存映射器的一个节点(select|insert|delete|update),包括许多配置的sql、sql的id,缓存信息等
  • SqlSource,提供BoundSql对象,是MappedStatement的属性。负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  • BoundSql,表示动态生成的SQL语句以及相应的参数信息。常用属性:SQL、parameterObject、parameterMappings

    • parameterObject为传入的参数本身

    • parameterMappings,它是一个List,装parameterMapping对象,包括参数属性,名称,表达式,javaType,jdbcType、typeHandler等信息

    • sql,既是写在映射器中的一条SQL

2.SqlSession四大对象

Mapper执行过程是通过Executor、StatementHandler、ParameterHandler、ResultHandler来完成数据库操作和结果返回的

  • Executor 执行器,调度StatementHandler、ParameterHandler、ResultHandler等来执行对应SQL
  • StatementHandler 数据库会话器,使用数据库的Statement、PreparedStatement执行操作,核心
  • ParameterHandler 参数处理器,用于SQL对参数的处理
  • ResultHandler 结果处理器,对数据集的封装返回

二.SqlSession运行过程

SqlSession用途

  1. 获取映射器
  2. 直接通过命名信息去执行SQL返回结果
    推荐使用映射器+xml方式

    1. 映射器动态代理对象执行过程

    调用SqlSession中的getMapper方法,传入类对象。会通过类对象获得对应的MapperProxyFactory,构建实例,动态代理MapperProxy为通知类。生成动态代理对象,代理方法放到MapperProxy中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class MapperProxy<T> implements InvocationHandler, Serializable {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
    }

    if (this.isDefaultMethod(method)) {
    return this.invokeDefaultMethod(proxy, method, args);
    }
    } catch (Throwable var5) {
    throw ExceptionUtil.unwrapThrowable(var5);
    }

    MapperMethod mapperMethod = this.cachedMapperMethod(method);
    return mapperMethod.execute(this.sqlSession, args);
    }
    }

判断,如果是接口,则调用下面的方法,判断如果是默认的方法,如toString等方法,则直接调用。生成MapperMethod方法对象,执行该对象的execute方法传入sqlSession和当前参数,在该execute中进行具体的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
switch(this.command.getType()) {
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
default://抛错
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
//抛错
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;//返回sql的执行结果
}
}

MappedStatement有SqlCommandType属性(存是select/insert等类型),在MappedMethod构造时,SqlCommandType放进MappedMethod内部的SqlCommand中的type(xml中的标签类型)来判断执行那一段方法,如为查询语句时即SELECT,根据返回类型来判断具体执行哪个方法
举例:执行查询语句,调用到executeForMany方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}

if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
}

最后通过SqlSession调用 ,通过MapperMethod的SqlCommand属性调用getName方法获得接口名+方法名的调用,探究的关键就在于MapperMethod在创建的时候就在SqlCommand的name中初始化好了名称,具体实现如下,看MapperProxy的invoke中的MapperMethod mapperMethod = this.cachedMapperMethod(method);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}

return mapperMethod;
}
//MapperMethod的构造
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
//SqlCommand的构造
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String methodName = method.getName();
Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) == null) {
throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
}
this.name = null;
this.type = SqlCommandType.FLUSH;
} else {
this.name = ms.getId();//这里将MappedStatement中的id属性进行赋值给name:接口名+方法名
this.type = ms.getSqlCommandType();
if (this.type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + this.name);
}
}
}

MapperMethod的内部类MethodSignature,主要作用是对Method方法做一步封装,比如: 提供判断方法返回值是否为NULL或Map等方法,及提供处理接口传入参数,封装转换成SqlSession所要的参数

在SqlCommand中,两个存放的信息,name 负责存放调用的目标方法名 ,type 负责存放SQL语句的类型(SELECT|INSERT|UPDATE等)。

通过SqlCommand构造函数可以看到,this.name = ms.getId();其对name进行了赋值,值为对应xml文件中对应方法的id,怎么找到的这个MappedStatement的?
看这行代码MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
} else {
Class[] var6 = mapperInterface.getInterfaces();
int var7 = var6.length;
for(int var8 = 0; var8 < var7; ++var8) {
Class<?> superInterface = var6[var8];
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = this.resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
}

可以看到,mybaits将调用的接口名与方法名的字符串,在MappedStatements中找对应的xml中对应的MappedStatement结点,从而调用到对应的xml写的sql。(MappedStatements是Configuration的一个属性protected final Map<String, MappedStatement> mappedStatements;)

2. 底层执行过程

在创建SqlSession时,已经创建了Executor

1
2
3
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}

具体的创建方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}

if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}

已分析SimpleExecutor为例,上面我们看到调用映射器中的查询方法最终调用到SqlSession的查询selectList

1
2
3
4
5
6
7
8
9
10
11
12
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}

这里获取该名字对应MappedStatement结点,执行executor的query方法,最终调用到真正的执行查询的方法doQuery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}

return var9;
}

可以看到,创建一个数据库会话器StatementHandler,执行StatementHandler的prepareStatement方法,进行初始化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}

//prepare方法
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(this.boundSql.getSql());
Statement statement = null;

try {
statement = this.instantiateStatement(connection);
this.setStatementTimeout(statement, transactionTimeout);
this.setFetchSize(statement);
return statement;
} catch (SQLException var5) {
this.closeStatement(statement);
throw var5;
} catch (Exception var6) {
this.closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + var6, var6);
}
}
//parameterize方法
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}

调用prepare方法设置最大行数、超时时间等基本操作,parameterize方法调用参数处理器绑定参数,执行StatementHandler的prepareStatement方法后调用,Executor的query方法

1
2
3
4
5
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = this.boundSql.getSql();
statement.execute(sql);
return this.resultSetHandler.handleResultSets(statement);
}

执行对应的语句,并调用结果处理器返回参数

三.总结

在映射器+xml调用中

  1. 获取映射器,getMapper通过动态代理,获得代理对象
  2. 在调用相关接口方法时,拦截调用相关方法,用接口名+方法名,在MapperStatements中查找对应的id的MapperStatement,有则,判断该id的xml片断的sql标签,调用sqlsession的对应方法,将接口名+方法名,参数传入
  3. 调用Executor的query/update方法,创建数据库会话器,调用prepare、parameterize(调用参数处理器绑定参数),query/update(query需要返回封装结果,调用结果处理器。update中返回处理行数,直接返回int类型)

第二点 - 参数处理过程

一.完成参数索引与参数名之间的对应

在动态代理中,通过cachedMapperMethod创建MapperMethod,构建了MethodSignature对象,其中有一个属性为paramNameResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
//MapperMethod的构造
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
//MethodSignature初始化
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class) {
this.returnType = (Class)resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class)((ParameterizedType)resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}

this.returnsVoid = Void.TYPE.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = this.getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}

查看paramNameResolver初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;

public ParamNameResolver(Configuration config, Method method) {
//存储目标方法的参数对应的Class对象
Class<?>[] paramTypes = method.getParameterTypes();
//存储目标方法的注解对象数组,每一个方法的参数都有一个注解数组
Annotation[][] paramAnnotations = method.getParameterAnnotations();
SortedMap<Integer, String> map = new TreeMap();
//存储目标方法的参数个数
int paramCount = paramAnnotations.length;

for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
if (!isSpecialParameter(paramTypes[paramIndex])) {
String name = null;
Annotation[] var9 = paramAnnotations[paramIndex];
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
Annotation annotation = var9[var11];
if (annotation instanceof Param) {
this.hasParamAnnotation = true;
name = ((Param)annotation).value();
break;
}
}

if (name == null) {
if (config.isUseActualParamName()) {
name = this.getActualParamName(method, paramIndex);
}

if (name == null) {
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
}
this.names = Collections.unmodifiableSortedMap(map);
}

  1. isSpecialParameter(paramTypes[paramIndex])判断是否是RowBounds和ResultHandler特殊类型。如果true,则跳过.
  2. 判断每一个注解是否是Param注解
    • true,则hasParamAnnotation赋值为true,表示该方法有@Param注解,然后直接把Param注解的value值赋值给name
    • false,没有使用@Param注解,则判断是否配置中开启了useActualParamName
      • true,则调用getActualParamName方法,并通过ParamNameUtil工具类获取目标方法的参数名,再把参数名存储到List中,接着根据传入的索引获取对应的参数名.然后把参数索引和参数名存放到map中.
      • false,那么就会使用参数索引作为name.

当所有参数都判断之后,通过Collections.unmodifiableSortedMap(map)返回一个只读的Map容器赋值给names,同样存放着参数索引和 参数名的映射关系。此时MethodSignature初始化完毕

useActualParamName3.4.1开始可以配置true|false。3.4.2之前为false。之后为true

例子:这是在多个参数的时候,才会封装成map,return map。如果只有一个参数则直接返回该参数image-20191226201723181
Employee findByIdAndNameWithGender(Integer id, @Param("myName")String name, String gender);

  • 当useActualParamName()为false时:
    0 -> “0”
    1 -> “myName”
    2 -> “2”
  • 当useActualParamName()为true时:
    0 -> “arg0”
    1 -> “myName”
    2 -> “arg2”

二.使用对应关系,绑定参数名与具体参数对象

在之前的executeForMany中有这样的一个方法
Object param = this.method.convertArgsToSqlCommandParam(args);具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}

if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
}

//MapperMethod的方法convertArgsToSqlCommandParam
public Object convertArgsToSqlCommandParam(Object[] args) {
return this.paramNameResolver.getNamedParams(args);
}
//ParamNameResolver的方法getNamedParams
public Object getNamedParams(Object[] args) {
int paramCount = this.names.size();
if (args != null && paramCount != 0) {
if (!this.hasParamAnnotation && paramCount == 1) {
return args[(Integer)this.names.firstKey()];
} else {
Map<String, Object> param = new ParamMap();
int i = 0;
for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
Entry<Integer, String> entry = (Entry)var5.next();
param.put(entry.getValue(), args[(Integer)entry.getKey()]);
String genericParamName = "param" + String.valueOf(i + 1);
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[(Integer)entry.getKey()]);
}
}
return param;
}
} else {
return null;
}
}
  1. 遍历Map容器(即之前参数索引与参数名对应的map)names,以value作为key和args数组对应索引的值作为value存储到Map容器param中
  2. 根据GENERIC_NAME_PREFIX常量即”param”和当前的索引拼装成新的字符串,即param1,param2,…,paramN.然后和args数组里对应索引的值存储到Map容器param中

上面举得例子得到的结果 image-20191226185841103

参考文章:https://blog.csdn.net/chenruicsdn/article/details/81370219,这篇博文有一定的错误

0%